package com.googlecode.cswish.struts.hibernate;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.persistence.CascadeType;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinTable;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import org.hibernate.annotations.common.util.StringHelper;
import org.hibernate.cache.internal.CacheDataDescriptionImpl;
import org.hibernate.cache.spi.EntityRegion;
import org.hibernate.cache.spi.QueryCache;
import org.hibernate.cache.spi.RegionFactory;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.HbmBinder;
import org.hibernate.cfg.Mappings;
import org.hibernate.cfg.Settings;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.query.spi.QueryPlanCache;
import org.hibernate.engine.spi.CacheImplementor;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.mapping.Bag;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.JoinedSubclass;
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.OneToMany;
import org.hibernate.mapping.OneToOne;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.SingleTableSubclass;
import org.hibernate.mapping.Subclass;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UnionSubclass;
import org.hibernate.mapping.Value;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.spi.PersisterFactory;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.type.CompositeType;

import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.struts.conversion.PageConverter;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.util.ClassUtil;
import com.googlecode.cswish.util.bean.ModelUtil;


/**
 * Maintain Hibernate model meta data - Add/Remove model field
 * 
 * @see HbmBinder, AnnotationBinder
 * 	 
 * @author jerry.feng Date: 2009-2-9
 */
public class DynamicModelPersistent {

	private static final Logger logger = Logger.getLogger(DynamicModelPersistent.class);
	
	private final static int FIELD_TYPE_SIMPLE = 0;
	private final static int FIELD_TYPE_ENTITY = 1;
	private final static int FIELD_TYPE_LIST = 2;
	private final static int FIELD_TYPE_EMBED = 3;
	private final static int FIELD_TYPE_ENUM = 4;
	
	// util service
	private BeanIntrospector beanIntrospector;
	private PageConverter converterManager;
	private HibernateConfiguration hibernateConfiguration;
	
	// entity name
    private String entityName;
    
    // identify the entity is embeddable class
    private boolean embeddableClass;
    
    // identify the Model is a new definition
    private boolean newDef;
    
    // the dynamic model is changed
    private boolean changed;
    
    // store the entity information
    private PersistentClass persistentClass;
    private List<EntityPersister> entityPersisters;
    private List<EntityPersister> superEntityPersisters;
    private List<EntityPersister> subEntityPersisters;

    // removed property name
    private Map<String, Property> removedProperties;
    
    // new property
    private Map<String, Property> newProperties;
    private Map<String, String> newPropertyTypes;
    
    private List<CollectionProperty> collectionProperties;
	
    private class CollectionProperty {
    	String property;
    	Element node;
    	
    	private CollectionProperty(String property, Element node) {
    		this.property = property;
    		this.node = node;
    	}
    }
    
    public DynamicModelPersistent(String entityName, String tableName, String joinTableName, BeanIntrospector beanIntrospector,
    		PageConverter converterManager, HibernateConfiguration hibernateConfiguration) {
        this.entityName = entityName;
        this.beanIntrospector = beanIntrospector;
        this.hibernateConfiguration = hibernateConfiguration;
        this.converterManager = converterManager;
        
        getPersistentClass(entityName, tableName, joinTableName);
    }
	
    public String getEntityName() {
		return entityName;
	}
    
    public boolean isNewDef() {
		return newDef;
	}

	/**
     * Update the dynamic model information according to the modelInfo
     * 
     * @param modelInfo
     */
    public void updateModelInfo(ModelInfo modelInfo, boolean commitPhase2, boolean forceUpdate) {
    	for (PropertyInfo propertyInfo : modelInfo.getAllProperties()) {
    		if (propertyInfo.isTableColumn() && !propertyInfo.isId()) {		// id defined in 'DynamicModelTemplate.xml'
    			boolean isCurrentClassProperty = propertyInfo.getReadMethod().getDeclaringClass()
    						.getName().equals(modelInfo.getType());
    			if (isCurrentClassProperty) {
    				int fieldType = getFieldType(propertyInfo);
    				
    				addDynamicField(propertyInfo.getName(), propertyInfo.getTableName(), propertyInfo.getColumnName(),
    						fieldType, propertyInfo.getType(), propertyInfo.getGenericType(), propertyInfo.getLength(),
    						propertyInfo.isNullable(), propertyInfo.isUnique(),
    						propertyInfo.isInsertable(), propertyInfo.isUpdatable(), null);
    			}
    		}
    	}
    	
    	commitPhase1(forceUpdate);
    	if (commitPhase2 || forceUpdate) {
    		commitPhase2(forceUpdate);
    	}
    }

	private int getFieldType(PropertyInfo propertyInfo) {
		int fieldType;
		if (propertyInfo.isSimpleObj()) {
			if (propertyInfo.getTypeClass().isEnum()) {
				fieldType = FIELD_TYPE_ENUM;
			} else {
				fieldType = FIELD_TYPE_SIMPLE;
			}
		} else {
			boolean isList = List.class.getName().equals(propertyInfo.getType());
			if (isList) {
				fieldType = FIELD_TYPE_LIST;
			} else {
				if (propertyInfo.getTypeClass().getAnnotation(Embeddable.class) != null) {
					fieldType = FIELD_TYPE_EMBED;
				} else {
					fieldType = FIELD_TYPE_ENTITY;
				}
			}
		}
		return fieldType;
	}
    
    public boolean addDynamicField(String name, String type, String genericType, int length, boolean nullable, boolean unique) {  	
    	if (embeddableClass) {
    		return false;			// TODO: support add new property to embeddable class?
    	}
    	
    	Property property = getProperty(name);
    	if (property != null) {
    		Value value = property.getValue();
    		String oldType;
    		boolean sameType;
    		if (value instanceof Component) {
    			oldType = ((Component)value).getComponentClassName();
    			sameType = oldType.equals(type);
    		} else if (value instanceof SimpleValue) {
    			oldType = ((SimpleValue)value).getTypeName();
    			if (oldType == null && value instanceof OneToOne) {
    				oldType = ((OneToOne)value).getReferencedEntityName();
    			}
    			sameType = oldType.equals(type);
    		} else {
    			oldType = value.getType().getName();
    			// TODO: is there better way to compare the type?
    			if (List.class.getName().equals(genericType) && oldType.startsWith("java.util.Collection(")) {
    				sameType = true;
    			} else {
    				sameType = oldType.equals(type);
    			}
    		}
    		if (sameType) {
    			return false;
    		} else {
    			//property.getValue().setType
    			removeDynamicField(name);
    		}
    	}
    	
		Class typeClass = ClassUtil.forName(type);
		boolean isSimpleObj = typeClass != null && (typeClass.isPrimitive() || Number.class.isAssignableFrom(typeClass)
    				|| String.class.equals(typeClass) || Date.class.isAssignableFrom(typeClass) 
    				|| type.startsWith("java.lang.") || typeClass.isEnum());
		int fieldType;
		if (isSimpleObj) {
			fieldType = FIELD_TYPE_SIMPLE;
		} else {
			boolean isList = typeClass != null && List.class.isAssignableFrom(typeClass);
			if (isList) {
				fieldType = FIELD_TYPE_LIST;
			} else {
				fieldType = FIELD_TYPE_ENTITY;
			}
		}
		return addDynamicField(name, null, null, fieldType, type, genericType, length, nullable, unique, true, true, null);
    }
    
    public boolean isChanged() {
		return changed;
	}
    
	public void setChanged(boolean changed) {
		this.changed = changed;
	}

	/**
	 * 
	 * @param name
	 * @param tableName
	 * @param columnName
	 * @param fieldType 0 -- simple object, 1 -- entity, 2 -- list, 3 -- embeddable entity
	 * @param type
	 * @param genericType
	 * @param length
	 * @param isNullable
	 * @param unique
	 * @param isInsertable
	 * @param isUpdatable
	 */
	public boolean addDynamicField(String name, String tableName, String columnName, int fieldType,
    		String type, String genericType, int length, boolean isNullable, boolean unique,
    		boolean isInsertable, boolean isUpdatable, String embeddableClassName) {
		if (embeddableClass) {
    		return false;
    	}
		
    	if (name == null) {
    		logger.error("can't add null property to hibernate model:" + entityName);
    		return false;
    	}
    	
    	changed = true;
    	
    	
		PersistentClass persistentClass = getPersistentClass(entityName);
		if ("java.lang.Object".equals(type)) {
			type = genericType;
		}
		
		if (columnName == null || columnName.length() == 0) {
			if (fieldType == FIELD_TYPE_ENTITY) {
				columnName = name + "_Id";
			} else if (fieldType == FIELD_TYPE_LIST) {
				String simpleName = entityName.substring(entityName.lastIndexOf('.') + 1);
				columnName = StringUtils.uncapitalize(simpleName) + "_Id";
			} else {
				columnName = name;
			}
		}
		
		Property property = getProperty(persistentClass, name);
		boolean addNewProperty;
        // Add new property
        if (property == null) {
        	addNewProperty = true;
        	property = new Property();
        	// check if embeddable property contained this property name
        	boolean dupWithEmbedProp = existInComponent(persistentClass, name);
        	if (dupWithEmbedProp) {
        		// TODO: correct? or add new columnName?
        		isInsertable = false;
        		isUpdatable = false;
        	}
        } else {
        	addNewProperty = false;
        }
        property.setName(name);
        property.setNodeName(name);
        property.setUpdateable(isUpdatable);
        property.setInsertable(isInsertable);
        
		// see HbmBinder.bindComponent, bindSimpleValue, bindColumns
        // entrance: HbmBinder.createClassProperties
        Value value;
        if (fieldType == FIELD_TYPE_SIMPLE || fieldType == FIELD_TYPE_ENUM || fieldType == FIELD_TYPE_ENTITY) {
        	Column column = new Column(columnName);
        	Table table = persistentClass.getTable();

        	if (tableName != null) {
        		table = null;
        		Iterator iter = persistentClass.getJoinIterator();
        		while (iter.hasNext()) {
        			Join join = (Join) iter.next();
        			Table subclassTable = join.getTable();
        			if (tableName.equals(subclassTable.getName())) {
        				table = subclassTable;
        				join.addProperty(property);
        				addNewProperty = false;
        				break;
        			}
        		}
        		if (table == null) {
        			logger.error("Can't find the mapping table: " + column);
        		}
        	}
        	
        	List<Configuration> configs = hibernateConfiguration.getConfigurations();
			Configuration config = configs.iterator().next();
        	Mappings mappings = config.createMappings();
        	if (table != null) {
	        	table.addColumn(column);
	        	String propertyPath = embeddableClassName == null ? entityName + '.' + name : name;
	        	String logicalColumnName = mappings.getNamingStrategy().logicalColumnName(
					columnName, propertyPath
				);
	    		mappings.addColumnBinding(logicalColumnName, column, table);
        	}
        	if (length != 0) {
            	column.setLength(length);
            }
            column.setNullable(isNullable);
            column.setUnique(unique);
            
            if (fieldType == FIELD_TYPE_ENTITY) {
            	ManyToOne manyToOne = new ManyToOne(mappings, table);
            	manyToOne.addColumn(column);
            	manyToOne.setTypeName(type);
            	manyToOne.setReferencedEntityName(type);
            	property.setCascade("merge");
            	value = manyToOne;
            } else {	// FIELD_TYPE_SIMPLE
            	SimpleValue simpleValue = new SimpleValue(mappings, table);
            	simpleValue.addColumn(column);
            	simpleValue.setTypeName(type);
            	if (fieldType == FIELD_TYPE_ENUM) {
            		addTypeDef4Enum(name, genericType, mappings, simpleValue);
            	}
            	value = simpleValue;
            }
        } else if (fieldType == FIELD_TYPE_EMBED) {
        	List<Configuration> configs = hibernateConfiguration.getConfigurations();        	
        	Configuration config = configs.iterator().next();
        	Mappings mappings = config.createMappings();
    		Component component = new Component(mappings, persistentClass);
    		component.setEmbedded(true);
    		component.setComponentClassName(type);
    		component.setNodeName(name);
    		
    		Element bagNode = new DefaultElement("component");
        	bagNode.addAttribute("name", name);
        	ModelInfo modelInfo = beanIntrospector.getModelInfo(type);
    		for (PropertyInfo propertyInfo : modelInfo.getAllProperties()) {
    			if (propertyInfo.isTableColumn()) {
    				bagNode.addElement("property").addAttribute("name", propertyInfo.getName());
    			}
    		}
    		
    		HbmBinder.bindComponent(bagNode, component, entityName, 
    				name, entityName + '.' + name, isNullable,
    				false, mappings, new HashMap(), 
    				false);
        	value = component;
        } else if (fieldType == FIELD_TYPE_LIST) {	// see HbmBinder.bindCollection, bindOneToMany, HbmBinder.BAG.create
        	List<Configuration> configs = hibernateConfiguration.getConfigurations();        	
        	Configuration config = configs.iterator().next();
        	Mappings mappings = config.createMappings();
        	String path = entityName + '.' + name;
        	Bag bag = new Bag(mappings, persistentClass);
        	if (collectionProperties == null) {
        		collectionProperties = new ArrayList<CollectionProperty>();
        	}
        	
			Map inheritedMetas = new HashMap();
			Element node = buildListMapping(name, genericType, property);
			HbmBinder.bindCollection(node, bag, persistentClass.getEntityName(), path, mappings, inheritedMetas);        	
        	collectionProperties.add(new CollectionProperty(property.getName(), node));
        	
        	// FIXME: parse ManyToMany
        	if (bag.getElement() == null) {
	        	
        	}
        	
        	/*
        	bag.setRole(entityName + '.' + name);
        	bag.setNodeName(name);
        	bag.setInverse(true);
			OneToMany oneToMany = new OneToMany(persistentClass);
			bag.setElement(oneToMany);
			// the referenced name is parent class or child class? (home: com.espirit.c1.sub1.Datagrid11)
			oneToMany.setReferencedEntityName(genericType);
			oneToMany.setAssociatedClass(persistentClass);
			
			Table collectionTable = getPersistentClass(genericType).getTable();
			bag.setCollectionTable(collectionTable);
			KeyValue keyVal = bag.getOwner().getIdentifier();
			SimpleValue key = new DependantValue(collectionTable, keyVal );
			bag.setKey(key);
			//key.setCascadeDeleteEnabled(true);
			String mappedByColumn = getMappedByColumn(name);
			if (mappedByColumn != null && mappedByColumn.length() > 0) {
				columnName = mappedByColumn;
			}
			property.setCascade("all");
			bindListAnno(name, bag, property);
			
			Column column = new Column(columnName);
			key.addColumn(column);*/
        	if (mappings.getCollection(path) == null) {
				mappings.addCollection(bag);
        	}
        	if (bag.getElement() == null) {
        		OneToMany oneToMany = new OneToMany(mappings, persistentClass);
    			bag.setElement(oneToMany);
    			// FIXME: target element has error
    			// the referenced name is parent class or child class? (home: com.espirit.c1.sub1.Datagrid11)
    			oneToMany.setReferencedEntityName(genericType);
    			oneToMany.setAssociatedClass(persistentClass);
        	}
        	
        	String propertyRef = bag.getReferencedPropertyName();
			// not necessarily a *unique* property reference
			if ( propertyRef != null ) {
				mappings.addPropertyReference( bag.getOwnerEntityName(), propertyRef );
			}
			value = bag;
        } else {
        	value = null;			// impossible logic
        }
        
        if (embeddableClassName != null) {
        	value.setTypeUsingReflection(embeddableClassName, name);
        } else {
        	value.setTypeUsingReflection(entityName, name);
        }
        value.createForeignKey();
        property.setValue(value);
        if (addNewProperty) {	        
	        if (newProperties == null) {
	        	newProperties = new LinkedHashMap<String, Property>();
	        	newPropertyTypes = new LinkedHashMap<String, String>();
	        }
	        newProperties.put(property.getName(), property);
	        newPropertyTypes.put(property.getName(), type);
	        return true;
        } else {
        	return false;
        }
    }
	
	private List<CollectionPersister> bindCollectionSecondPass() {
		List<String> listPropertyNames = new ArrayList<String>();
		if (newPropertyTypes != null) {
	        for (Entry<String, String> entry : newPropertyTypes.entrySet()) {
	        	boolean isList = List.class.getName().equals(entry.getValue());
	        	if (isList) {
	        		listPropertyNames.add(entry.getKey());
	        	}
	        }
		}
		if (collectionProperties == null && listPropertyNames.size() == 0) {
			return null;
		}
		
		List<Configuration> configs = hibernateConfiguration.getConfigurations();
		Configuration config = configs.iterator().next();
    	Mappings mappings = config.createMappings();
    	if (this.collectionProperties != null) {
	    	for (CollectionProperty cp : this.collectionProperties) {
	    		Property property = getProperty(cp.property);
	    		Element node = cp.node;
		    	Collection bag = (Collection) property.getValue();
				Map inheritedMetas = new HashMap();
				List secondPasses = (List) getField(config, Configuration.class, "secondPasses");
				Map persistentClasses = (Map) getField(config, Configuration.class, "classes");
				
		    	HbmBinder.bindCollectionSecondPass(node, bag, persistentClasses, mappings, inheritedMetas);
		    	secondPasses.remove(secondPasses.size() - 1);
	    	}
    	}
    	
    	List<CollectionPersister> newCollectionPersisters = null;
    	if (listPropertyNames.size() > 0) {
			SessionFactoryImpl sessionFactory = (SessionFactoryImpl)hibernateConfiguration.getSessionFactory();
			Map collectionPersisters = sessionFactory.getCollectionPersisters();
			ServiceRegistryImplementor serviceRegistry = sessionFactory.getServiceRegistry();
			PersisterFactory persisterFactory = serviceRegistry.getService( PersisterFactory.class );
			newCollectionPersisters  = new ArrayList<CollectionPersister>(listPropertyNames.size());
			
    		for (String name : listPropertyNames) {
    			Property property  = getProperty(persistentClass, name);
	    		org.hibernate.mapping.Collection collection = (org.hibernate.mapping.Collection) property.getValue();
				// NOTICE: collection depends on itself
				CollectionPersister persister = persisterFactory.createCollectionPersister(config, collection, null, sessionFactory) ;
				newCollectionPersisters.add(persister);
				collectionPersisters.put( property.getPersistentClass().getEntityName() + '.' + name,
						persister.getCollectionMetadata() );
    		}
		}
    	return newCollectionPersisters;
	}

	private void addEmbeddedProperties(String type) {
		ModelInfo modelInfo = beanIntrospector.getModelInfo(type);
		for (PropertyInfo propertyInfo : modelInfo.getAllProperties()) {
			if (propertyInfo.isTableColumn()) {
				// path, table
				int fieldType = getFieldType(propertyInfo);
				
				addDynamicField(propertyInfo.getName(), null, propertyInfo.getColumnName(),
						fieldType, propertyInfo.getType(), propertyInfo.getGenericType(), propertyInfo.getLength(),
						propertyInfo.isNullable(), propertyInfo.isUnique(),
						propertyInfo.isInsertable(), propertyInfo.isUpdatable(), type);
			}
		}
	}

	private Element buildListMapping(String name, String genericType, Property property) {
		/*
		<bag name="employees" table="employee"
            inverse="true" lazy="true" fetch="select">
            <key>
                <column name="department_id" not-null="true"/>
            </key>
            <one-to-many class="net.viralpatel.hibernate.Employee">	(case 1)
            </one-to-many>
            
            <many-to-many class="com.mkyong.stock.Stock">		(case 2)
                <column name="STOCK_ID" not-null="true" />
            </many-to-many>
    	</bag>
    	 */
		
		Element bagNode = new DefaultElement("bag");
    	bagNode.addAttribute("name", name);
    	String simpleClassName = genericType.substring(genericType.lastIndexOf('.') + 1);
    	// TODO: correct?
    	String tableName = StringUtils.uncapitalize(simpleClassName);
    	bagNode.addAttribute("table", tableName);
    	bagNode.addAttribute("lazy", "true");
    	bagNode.addAttribute("cascade", "all");
    	property.setCascade("merge");
    	bagNode.addAttribute("fetch", "select");
    	bagNode.addAttribute("inverse", "true");
    	Element keyNode = bagNode.addElement("key");
    	
		javax.persistence.OneToMany otm = null;
		javax.persistence.ManyToMany mtm = null;
		JoinTable joinTable = null;
		FetchType fetch = null;
		CascadeType[] cascadeTypes = null;
		Class targetEntity = null;
		String mappedBy = null;
		PropertyInfo propInfo = beanIntrospector.getModelInfo(entityName).getProperty(name);
		if (propInfo != null) {
			Method readMethod = propInfo.getReadMethod();
			if (readMethod != null) {
				otm = readMethod.getAnnotation(javax.persistence.OneToMany.class);
				if (otm != null) {
					fetch = otm.fetch();
					cascadeTypes = otm.cascade();
					mappedBy = otm.mappedBy();
					targetEntity = otm.targetEntity();
				} else {
					mtm = readMethod.getAnnotation(javax.persistence.ManyToMany.class);
					if (mtm != null) {
						fetch = mtm.fetch();
						cascadeTypes = mtm.cascade();
						mappedBy = mtm.mappedBy();
						targetEntity = mtm.targetEntity();
						joinTable = readMethod.getAnnotation(JoinTable.class);
					}
				}
				
				boolean isLazy = FetchType.LAZY.equals(fetch);
				bagNode.addAttribute("lazy", String.valueOf(isLazy));
				property.setLazy(isLazy);
				
				if (cascadeTypes != null && cascadeTypes.length > 0) {
					StringBuilder cascade = new StringBuilder();
					for (CascadeType cascadeType : cascadeTypes) {
						cascade.append(cascadeType).append(',');
					}
					bagNode.addAttribute("cascade", cascade.substring(0, cascade.length()).toLowerCase());
				}
				
				if (joinTable != null) {
					
				}
			}
		}
		
		Element manyToManyEle = null;
		if (propInfo == null || otm != null) {
	    	Element oneToManyEle = bagNode.addElement("one-to-many");
	    	Class clazz = ClassUtil.forName(this.entityName);
	    	Class superClass = clazz;
	    	while (superClass != null && superClass.getAnnotation(Entity.class) != null) {
	    		clazz = superClass;
	    		superClass = superClass.getSuperclass();
	    	}
	    	String columnName = StringUtils.uncapitalize(clazz.getSimpleName());
	    	keyNode.addAttribute("column", columnName + "_id");
	    	oneToManyEle.addAttribute("class", genericType);
		} else {
			manyToManyEle = bagNode.addElement("many-to-many");
			manyToManyEle.addAttribute("class", genericType);
			if (joinTable != null && joinTable.joinColumns().length > 0) {
				keyNode.addAttribute("column", joinTable.joinColumns()[0].name());
				manyToManyEle.addAttribute("column", joinTable.inverseJoinColumns()[0].name());
			}
		}
		
		if (joinTable != null) {
			bagNode.addAttribute("table", joinTable.name());
		}
		if (targetEntity != null) {
			PropertyInfo targetPropertyInfo = beanIntrospector.getModelInfo(targetEntity.getName()).getProperty(mappedBy);
			if (targetPropertyInfo != null) {
				Method targetReadMethod = targetPropertyInfo.getReadMethod();
				javax.persistence.Column targetColumn = targetReadMethod.getAnnotation(javax.persistence.Column.class);
				String mappedColumn;
				JoinTable targetJoinTable = null;
				if (targetColumn != null && targetColumn.name().length() > 0) {
					mappedColumn = targetColumn.name();				// the changed name
				} else {
					// Special case: Menu.JoinTable
					targetJoinTable = targetReadMethod.getAnnotation(JoinTable.class);
					if (targetJoinTable != null) {
						mappedColumn = targetJoinTable.inverseJoinColumns()[0].name();
					} else {
						mappedColumn = targetPropertyInfo.getName() + "_Id";	// the original property name
					}
				}
				
				if (mappedColumn != null && mappedColumn.length() > 0) {
					if (targetJoinTable != null) {
						bagNode.addAttribute("table", targetJoinTable.name());
						manyToManyEle.addAttribute("column", targetJoinTable.joinColumns()[0].name());
					}
					keyNode.addAttribute("column", mappedColumn);
					bagNode.addAttribute("inverse", "true");
				}
			}
		}
    	return bagNode;
	}
	
	private void bindListAnno(String name, org.hibernate.mapping.Collection collection, Property property) {
		PropertyInfo propInfo = beanIntrospector.getModelInfo(entityName).getProperty(name);
		if (propInfo != null) {
			Method readMethod = propInfo.getReadMethod();
			if (readMethod != null) {
				javax.persistence.OneToMany otm = readMethod.getAnnotation(javax.persistence.OneToMany.class);
				JoinTable joinTable = null;
				FetchType fetch = null;
				CascadeType[] cascadeTypes = null;
				if (otm != null) {
					fetch = otm.fetch();
					cascadeTypes = otm.cascade();
				} else {
					javax.persistence.ManyToMany mtm = readMethod.getAnnotation(javax.persistence.ManyToMany.class);
					if (mtm != null) {
						fetch = mtm.fetch();
						cascadeTypes = mtm.cascade();
						joinTable = readMethod.getAnnotation(JoinTable.class);
					}
				}
				
				boolean isLazy = FetchType.LAZY.equals(fetch);
				collection.setLazy(isLazy);
				if (cascadeTypes != null && cascadeTypes.length > 0) {
					StringBuilder cascade = new StringBuilder();
					for (CascadeType cascadeType : cascadeTypes) {
						cascade.append(cascadeType).append(',');
					}
					property.setCascade(cascade.substring(0, cascade.length()).toLowerCase());
				}
				
				if (joinTable != null) {
					
				}
			}
		}
	}
	
	private void addTypeDef4Enum(String name, String genericType, Mappings mappings, SimpleValue simpleValue) {
		PropertyInfo propInfo = beanIntrospector.getModelInfo(entityName).getProperty(name);
		String typeName = null;
		if (propInfo != null) {
			Method readMethod = propInfo.getReadMethod();
			if (readMethod != null) {
				org.hibernate.annotations.Type typeAnn = readMethod.getAnnotation(org.hibernate.annotations.Type.class);
				if (typeAnn != null) {
					typeName = typeAnn.type();
				}
			}
		}
		if (typeName == null) {
			// guess the type name
			typeName = genericType.substring(genericType.lastIndexOf('.') + 1);
		}
		org.hibernate.mapping.TypeDef typeDef = mappings.getTypeDef(typeName);
		if (typeDef != null) {
			simpleValue.setTypeName(typeDef.getTypeClass());
			simpleValue.setTypeParameters(typeDef.getParameters());
		} else {
			logger.error("Wrong Enum property, can't find the @TypeDef");
		}
	}

	
	
	private String getMappedByColumn(String name) {
		PropertyInfo propertyInfo = beanIntrospector.getModelInfo(entityName).getProperty(name);
		if (propertyInfo != null) {
			Method readMethod = propertyInfo.getReadMethod();
			if (readMethod != null) {
				javax.persistence.OneToMany otm = readMethod.getAnnotation(javax.persistence.OneToMany.class);
				String mappedBy = null;
				Class targetEntity = null;
				if (otm != null) {
					mappedBy = otm.mappedBy();
					targetEntity = otm.targetEntity();
				} else {
					javax.persistence.ManyToMany mtm = readMethod.getAnnotation(javax.persistence.ManyToMany.class);
					if (mtm != null) {
						mappedBy = mtm.mappedBy();
						targetEntity = mtm.targetEntity();
					}
				}
				
				if (targetEntity != null) {
					PropertyInfo targetPropertyInfo = beanIntrospector.getModelInfo(targetEntity.getName()).getProperty(mappedBy);
					if (targetPropertyInfo != null) {
						Method targetReadMethod = targetPropertyInfo.getReadMethod();
						javax.persistence.Column targetColumn = targetReadMethod.getAnnotation(javax.persistence.Column.class);
						String mappedColumn;
						if (targetColumn != null && targetColumn.name().length() > 0) {
							mappedColumn = targetColumn.name();				// the changed name
						} else {
							// Special case: Menu.JoinTable
							JoinTable joinTable = targetReadMethod.getAnnotation(JoinTable.class);
							if (joinTable != null) {
								mappedColumn = joinTable.inverseJoinColumns()[0].name();	// TODO:
							} else {
								mappedColumn = targetPropertyInfo.getName() + "_Id";	// the original property name
							}
						}
						return mappedColumn;
					}
				}
			}
		}
		return null;
	}
   
    public List<Property> getAllProperties() {
    	List<Property> properties = new ArrayList<Property>();
    	
    	PersistentClass persistentClass = getPersistentClass(entityName);
    	Iterator iterator = persistentClass.getPropertyClosureIterator();
		Property identifierProperty = persistentClass.getIdentifierProperty();
		if (identifierProperty != null) {
			properties.add(identifierProperty);
		}
		
		while (iterator.hasNext()) {
			Property prop = (Property) iterator.next();
			properties.add(prop);
		}
    	
    	return properties;
    }

    public Property getProperty(String name) {
    	PersistentClass persistentClass = getPersistentClass(entityName);
    	
    	return getProperty(persistentClass, name);
    }
    
    private Property getProperty(PersistentClass persistentClass, String name, boolean containChildClass) {
    	Property property = getProperty(persistentClass, name);
    	if (property == null && containChildClass) {
    		Iterator iter = persistentClass.getSubclassIterator();
    		while (iter.hasNext()) {
    			PersistentClass subClass = (PersistentClass) iter.next();
    			
    			property = getProperty(subClass, name, containChildClass);
    			if (property != null) {
    				break;
    			}
    		}
    	}
    	return property;
    }
    
    private Property getProperty(PersistentClass persistentClass, String name) {
		Property identifierProperty = persistentClass.getIdentifierProperty();
		String propertyName = StringHelper.root(name);
		if ( identifierProperty != null
				&& propertyName.equalsIgnoreCase(identifierProperty.getName())
				) {
			return identifierProperty;
		}
		else {
			Iterator iterator = persistentClass.getPropertyClosureIterator();
			while (iterator.hasNext()) {
				Property prop = (Property) iterator.next();
				if (prop.getValue().getType() instanceof CompositeType) { // TODO: right?
					CompositeType componentType = (CompositeType) prop.getValue().getType();
					String[] propertyNames = componentType.getPropertyNames();
					for (String propName : propertyNames) {
						if (propertyName.equalsIgnoreCase(propName)) {

						}
					}
				}
				if ( propertyName.equalsIgnoreCase(prop.getName())) {
					return prop;
				}
			}
		}
		return null;
    }
    
    public java.util.Collection<Property> getNewProperties() {
		return newProperties == null? null : newProperties.values();
	}

    public java.util.Collection<Property> getRemovedProperties() {
    	return removedProperties == null ? null : removedProperties.values();
    }
    
	private boolean existInComponent(PersistentClass persistentClass, String name) {
    	Iterator iterator = persistentClass.getPropertyClosureIterator();
    	String propertyName = StringHelper.root(name);
    	while (iterator.hasNext()) {
			Property prop = (Property) iterator.next();
			if (prop.getValue().getType() instanceof CompositeType) {
				CompositeType componentType = (CompositeType) prop.getValue().getType();
				String[] propertyNames = componentType.getPropertyNames();
				for (String propName : propertyNames) {
					if (propertyName.equals(propName)) {
						return true;
					}
				}
			}
    	}
    	return false;
    }

    public void removeDynamicField(String name) {    	
    	Property property = getProperty(getPersistentClass(entityName), name, true);
    	if (property != null) {
    		if (removedProperties == null) {
    			removedProperties = new HashMap<String, Property>();
    		}
    		removedProperties.put(property.getName(), property);
    		changed = true;
    	}
    }
    
    public void dropSelf() {
    	String simpleModelName = beanIntrospector.getSimpleName(entityName);
    	converterManager.resetPage(simpleModelName);
    	
    	List<Configuration> configs = hibernateConfiguration.getConfigurations();
    	for (Configuration config : configs) {
    		PersistentClass model = config.getClassMapping(entityName);
    		if (model != null) {
    			Map classes = (Map) getField(config, Configuration.class, "classes");
    			if (classes != null) {
    				classes.remove(entityName);
    			}
    			removeModelFromFactory(model);
    			break;
    		}
    	}
    }

    public synchronized boolean commitPhase1And2(boolean forceUpdate) {
    	boolean changed = commitPhase1(forceUpdate);
    	if (changed || forceUpdate) {
    		commitPhase2(forceUpdate);
    	}
    	return changed;
    }
    
    public synchronized boolean commitPhase1(boolean forceUpdate) {
    	if (changed || forceUpdate) {
    		PersistentClass persistentClass = getPersistentClass(entityName);
    		if (forceUpdate || removedProperties != null || newProperties != null || persistentClassChanged(persistentClass)) {
		        // lock the persistent class and update the changed field information to persistentClass
		    	synchronized (persistentClass) {		// TODO: use lock?
		    		this.entityPersisters = new ArrayList<EntityPersister>();
		    		Iterator iter = persistentClass.getSubclassIterator();
		    		if (iter.hasNext()) {
		    			List<PersistentClass> subList = new ArrayList<PersistentClass>();
		    			populateSubClass(persistentClass, subList);
		    			
		    			Map<String, EntityPersister> converted = new HashMap<String, EntityPersister>();
		    			for (PersistentClass subPersistentClass : subList) {
		    				boolean propertyChanged = updateClassProperty(subPersistentClass);
		    				if (propertyChanged || forceUpdate || persistentClassChanged(subPersistentClass)) {
			    				EntityPersister subPersister = updateSessionFactory(subPersistentClass.getEntityName(), true, converted);
			    				this.entityPersisters.add(subPersister);
		    				}
		    			}
		    		} else {
		    			boolean propertyChanged = updateClassProperty(persistentClass);
		    			if (propertyChanged || forceUpdate || persistentClassChanged(persistentClass)) {
			    			EntityPersister entityPersister = updateSessionFactory(this.entityName, true, new HashMap<String, EntityPersister>());
			    			this.entityPersisters.add(entityPersister);
		    			}
		    		}
				}
		    	return true;
    		} else {
    			changed = false;
    			return false;
    		}
    	} else {
    		return false;
    	}
    }
    
    private boolean persistentClassChanged(PersistentClass persistentClass) {
    	String oldClassName = persistentClass.getClassName();
    	String entityClassName = beanIntrospector.getModelInfo(entityName).getType();
    	return oldClassName != null && !oldClassName.equals(entityClassName);
    }
    
    private void populateSubClass(PersistentClass persistentClass, List<PersistentClass> subList) {
    	Iterator iter = persistentClass.getSubclassIterator();
    	while (iter.hasNext()) {
			PersistentClass subClass = (PersistentClass) iter.next();
			if (subClass.getSubclassIterator().hasNext()) {
				populateSubClass(subClass, subList);
			} else {
				subList.add(subClass);
			}
    	}
    }

	private boolean updateClassProperty(PersistentClass persistentClass) {
		boolean changed = false;
		if (removedProperties != null) {
			Iterator<Property> propertyIterator = persistentClass.getPropertyIterator();
			Iterator<Property> declaredPropertyIterator = persistentClass.getDeclaredPropertyIterator();
			
			boolean found = false;
			String columnName = null;
		    while (propertyIterator.hasNext()) {
		        Property property = propertyIterator.next();
		        if (removedProperties.containsKey(property.getName())) {
		            propertyIterator.remove();
		            Iterator iter = property.getColumnIterator();
		            if (iter.hasNext()) {
			            Column column = (Column)iter.next();
			            columnName = column.getName();
			            changed = true;
			            found = true;
		            }
		        }
		    }
		    if (found) {
		    	while (declaredPropertyIterator.hasNext()) {
		            Property property = declaredPropertyIterator.next();
		            if (removedProperties.containsKey(property.getName())) {
		            	declaredPropertyIterator.remove();
		            	changed = true;
		            }
		        }
		    }
		    if (columnName != null) {
			    Iterator<Column> columnIter = persistentClass.getTable().getColumnIterator();
			    while (columnIter.hasNext()) {
			    	if (columnName.equals(columnIter.next().getName())) {
			    		columnIter.remove();
			    		break;
			    	}
			    }
		    }
		    
		    
		    PersistentClass theClass = persistentClass.getSuperclass();
		    while (theClass != null) {
		    	List<Property> subclassProperties = (List<Property>)getField(theClass, PersistentClass.class, "subclassProperties");
		    	Iterator<Property> subPropertyIterator = subclassProperties.iterator();
		        while (subPropertyIterator.hasNext()) {
		        	Property property = subPropertyIterator.next();
		    		String name = property.getName();
		            if (removedProperties.containsKey(name)) {
		            	subPropertyIterator.remove();
		            	changed = true;
		            }
		        }
		        theClass = theClass.getSuperclass();
			}
		}
		
		if (newProperties != null) {
			for (Property property : newProperties.values()) {
				// check the property again, maybe, the super class added this property 
				if (getProperty(persistentClass, property.getName()) == null) {
					Property cloneProperty = new Property();
					ModelUtil.copy(property, cloneProperty);
					persistentClass.addProperty(cloneProperty);
					changed = true;
				}
			}
		}
		return changed;
	}
    
    //@see http://www.iteye.com/topic/197752
    //@see SessionFactoryImpl()
    @SuppressWarnings("unchecked")
	private EntityPersister updateSessionFactory(String entityName, boolean updateSupperEntity, Map<String, EntityPersister> converted) {
    	EntityPersister entityPersister = converted.get(entityName);
    	if (entityPersister != null) {
    		return entityPersister;
    	}
    	
    	PersistentClass model = getPersistentClass(entityName);
		
    	// NOTICE: the entity class name should be dynamic name
    	String entityClassName = beanIntrospector.getModelInfo(entityName).getType();
    	String oldClassName = model.getClassName();
    	
    	List<Configuration> configs = hibernateConfiguration.getConfigurations();
		Configuration config = configs.iterator().next();
		Mapping mapping = config.buildMapping();
		
		// FIXME: each site has their own SessionFactoryImpl (cfg.build) or SessionFactoryImpl can supports different site?
		SessionFactoryImpl sessionFactory = (SessionFactoryImpl)hibernateConfiguration.getSessionFactory();
		
		
		Properties properties = sessionFactory.getProperties();
		Settings settings = sessionFactory.getSettings();
		Dialect dialect = Dialect.getDialect(properties);
		ServiceRegistryImplementor serviceRegistry = sessionFactory.getServiceRegistry();
		
		if (!model.isInherited()) {
			IdentifierGenerator generator = model.getIdentifier()
					.createIdentifierGenerator(config.getIdentifierGeneratorFactory(), dialect,
							settings.getDefaultCatalogName(),
							settings.getDefaultSchemaName(), (RootClass) model);
			Map identifierGenerators = (Map) getField(sessionFactory, "identifierGenerators");
			if (!identifierGenerators.containsKey(model.getEntityName()))
				identifierGenerators.put(model.getEntityName(), generator);
		}

		model.prepareTemporaryTables(mapping, dialect);

		final String cacheRegionPrefix = settings.getCacheRegionPrefix() == null ? "" : settings.getCacheRegionPrefix() + ".";
		final String cacheRegionName = cacheRegionPrefix + model.getRootClass().getCacheRegionName();
		// cache region is defined by the root-class in the hierarchy...
		EntityRegionAccessStrategy accessStrategy = null;
		if (settings.isSecondLevelCacheEnabled() ) {
			CacheImplementor cacheAccess = serviceRegistry.getService( CacheImplementor.class );
			final AccessType accessType = AccessType.fromExternalName( model.getCacheConcurrencyStrategy() );
			if ( accessType != null ) {
				RegionFactory regionFactory = cacheAccess.getRegionFactory();
				EntityRegion entityRegion = regionFactory.buildEntityRegion( cacheRegionName, properties, CacheDataDescriptionImpl.decode( model ) );
				accessStrategy = entityRegion.buildAccessStrategy( accessType );
				cacheAccess.addCacheRegion( cacheRegionName, entityRegion );
			}
		}
		
		
		final PersisterFactory persisterFactory = serviceRegistry.getService( PersisterFactory.class );
		Map<String, EntityPersister> entityPersisters = sessionFactory.getEntityPersisters();
		EntityPersister oldEntityPersister = entityPersisters.get(model.getEntityName());
		if (oldEntityPersister != null) {
			// clear old cache
			EntityRegionAccessStrategy strategy = oldEntityPersister.getCacheAccessStrategy();
			if (strategy != null) {
				strategy.removeAll();
			}
		}
		
		// remove the class name and force the class to use dynamicMapTuplizer
		// model.setClassName(null);
		model.setClassName(entityClassName);
		model.setProxyInterfaceName(entityClassName);
		NaturalIdRegionAccessStrategy naturalIdAccessStrategy = null;	// if ( model.hasNaturalId()) ...
		entityPersister = persisterFactory.createEntityPersister(model,
				accessStrategy, naturalIdAccessStrategy, sessionFactory, mapping);
		
		entityPersisters.put(model.getEntityName(), entityPersister);
		if (oldClassName != null) {
			entityPersisters.put(oldClassName, entityPersister);
		}
		entityPersisters.put(entityClassName, entityPersister);
		converted.put(entityName, entityPersister);
		
		Map imports = (Map) getField(sessionFactory, "imports");
		String simpleName = entityName.substring(entityName.lastIndexOf('.') + 1);
		if (imports.get(simpleName) == null) {
			imports.put(simpleName, entityName);
		}
		
		// reset all query cache, better solution is only reset the affected cache
		QueryPlanCache queryPlanCache = sessionFactory.getQueryPlanCache();
		queryPlanCache.cleanup();
		
		// reset all the cached data
		QueryCache queryCache = sessionFactory.getQueryCache(null);
		queryCache.clear();
		
		// write back the cloned class mapping
		hibernateConfiguration.updateClassMapping(entityName, model);
		
		if (updateSupperEntity && model instanceof Subclass) {
			PersistentClass superClass = model.getSuperclass();
			EntityPersister superPersister = updateSessionFactory(superClass.getEntityName(), true, converted);
			if (superEntityPersisters == null) {
				superEntityPersisters = new ArrayList<EntityPersister>();
			}
			superEntityPersisters.add(superPersister);
		}
		
		/*
		Iterator iter = model.getSubclassIterator();
		while (iter.hasNext()) {
			PersistentClass subClass = (PersistentClass) iter.next();
			
			EntityPersister subPersister = updateSessionFactory(subClass.getEntityName(), false, converted);
			if (subEntityPersisters == null) {
				subEntityPersisters = new ArrayList<EntityPersister>();
			}
			subEntityPersisters.add(subPersister);
		}*/
		
		if (logger.isDebugEnabled()) {
			logger.debug("Rebuild hibernate persister: " + entityName + ", entityClassName=" + entityClassName);
		}
		return entityPersister;
	}
    
	public boolean commitPhase2(boolean forceUpdate) {
    	if (changed || forceUpdate) {
    		List<CollectionPersister> newCollectionPersisters = bindCollectionSecondPass();
    		
    		if (entityPersisters != null) {
    			for (EntityPersister entityPersister : entityPersisters) {
    				entityPersister.postInstantiate();
    			}
    		}
	    	if (superEntityPersisters != null) {
	    		for (EntityPersister superEntityPersister : superEntityPersisters) {
	    			superEntityPersister.postInstantiate();
	    		}
	    	}
	    	if (subEntityPersisters != null) {
	    		for (EntityPersister subEntityPersister : subEntityPersisters) {
	    			subEntityPersister.postInstantiate();
	    		}
	    	}
	    	if (newCollectionPersisters != null) {
	    		for (CollectionPersister collectionPersister : newCollectionPersisters) {
	    			collectionPersister.postInstantiate();
	    		}
	    	}
	        changed = false;
	        return true;
    	} else {
    		return false;
    	}
    }
    
    @SuppressWarnings("unchecked")
    private void removeModelFromFactory(PersistentClass model) {
    	String entityName = model.getEntityName();
    	SessionFactoryImpl sessionFactory = (SessionFactoryImpl)hibernateConfiguration.getSessionFactory();
    	Map identifierGenerators = (Map) getField(sessionFactory, "identifierGenerators");
    	if (identifierGenerators != null) {
    		identifierGenerators.remove(entityName);
    	}
    	String cacheRegion = model.getRootClass().getCacheRegionName();
    	Map allCacheRegions = (Map) getField(sessionFactory, "allCacheRegions");
    	if (allCacheRegions != null) {
    		allCacheRegions.remove(cacheRegion);
    	}
    	Map entityPersisters = (Map) getField(sessionFactory, "entityPersisters");
    	if (entityPersisters != null) {
    		entityPersisters.remove(entityName);
    	}
    }
    
    private Object getField(Object object, String fieldName) {
    	return getField(object, object.getClass(), fieldName);
    }
    
    private Object getField(Object object, Class clazz, String fieldName) {
    	Object value = null;
    	try {
	    	Field field = clazz.getDeclaredField(fieldName);
	    	field.setAccessible(true);
	    	value = field.get(object);
    	} catch (Exception ex) {
    		logger.error("Fail to get field: " + fieldName);
    	}
    	return value;
    }

    public PersistentClass getPersistentClass(String entityName) {
    	return getPersistentClass(entityName, null, null);
    }
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
	private PersistentClass getPersistentClass(String entityName, String tableName, String joinTableName) {
    	boolean isCurrentEntity = entityName.equals(this.entityName);
    	if (this.persistentClass != null && isCurrentEntity) {
    		return this.persistentClass;
    	}
    	
    	PersistentClass persistentClass = hibernateConfiguration.getClassMapping(entityName);
    	boolean embeddableClass = false;
    	boolean newDef = false;
    	if (persistentClass == null) {
    		Class superClazz = null;
    		Class entityClazz = null;
    		try {
    			entityClazz = Class.forName(entityName, true, Thread.currentThread().getContextClassLoader());
    			embeddableClass = (entityClazz.getAnnotation(Embeddable.class) != null);
    			
    			Class parent = entityClazz.getSuperclass();
    			
				if (parent.getAnnotation(Entity.class) != null) {
					superClazz = parent;
				}
    		} catch (Exception ex) {
    			if (logger.isDebugEnabled()) {    				
    				logger.debug("Don't find class: " + entityName);
    			}
    		}
    		
    		if (!embeddableClass) {    			
    			List<Configuration> configs = hibernateConfiguration.getConfigurations();
    			Configuration config = configs.iterator().next();
    			Mappings mappings = config.createMappings();
    			
    			if (superClazz != null) {
    				// see HbmBinder.handleSubclass, handleJoinedSubclass, handleUnionSubclass
    				// see AnnotationBinder.bindClass
	    			PersistentClass superMappedSuperclass = hibernateConfiguration.getClassMapping(superClazz.getName());
	    			boolean isJoinTable = false;	// TODO: it controls by @Inheritance.strategy
	    			
	    			// <subclass name="Employee" extends="Person" discriminator-value="E">
	    			Element node = new DefaultElement("subclass");		// joined-subclass, 
	    			node.addAttribute("name", entityName);
	    			int index = entityName.lastIndexOf('.');
	    			String simpleName = entityName.substring(index + 1);
	    			node.addAttribute("discriminator-value", simpleName);
					Map inheritedMetas = new HashMap();
					
					Subclass subclass;
					if (isJoinTable) {
						node.addAttribute("table", joinTableName);
						//UnionSubclass theSubclass = new UnionSubclass(superMappedSuperclass);
						//HbmBinder.bindUnionSubclass(node, theSubclass, mappings, inheritedMetas);
						
						//<joined-subclass name="Employee" extends="Person">
		                //<key column="person_id" />
						JoinedSubclass theSubclass = new JoinedSubclass(superMappedSuperclass);
						Element key = node.addElement("key");
						key.addAttribute("cascade", "on-delete");
						HbmBinder.bindJoinedSubclass(node, theSubclass, mappings, inheritedMetas );
						subclass = theSubclass;
					} else {
						Subclass theSubclass = new SingleTableSubclass(superMappedSuperclass);
						
						if (joinTableName != null) {
							/*	
							 	<join table="HEALTH_DETAILS">  
							        <key column="EMPLOYEE_ID"/>  
							        <property name="height"/>
							    </join>
							 */
							Element join = node.addElement("join");
							join.addAttribute("table", joinTableName);
							Element key = join.addElement("key");
							key.addAttribute("cascade", "on-delete");
							key.addAttribute("column", "ID");
						}
						HbmBinder.bindSubclass(node, theSubclass, mappings, inheritedMetas);
						subclass = theSubclass;
					}
					
					superMappedSuperclass.addSubclass(subclass);
					mappings.addClass(subclass);
					
					persistentClass = subclass;
    			} else {
    				/**
						<class entity-name="" table="">
					        <id name="id"
					            type="java.lang.Integer"
					            column="id">
					            <generator class="native" />
					        </id>
					    </class>
    				 */
    				RootClass rootClass = new RootClass();
    				Element node = new DefaultElement("class");
    				node.addAttribute("entity-name", entityName);
    				node.addAttribute("table", tableName);
    				Element idNode = node.addElement("id");
    				idNode.addAttribute("name", "id");
    				idNode.addAttribute("type", "java.lang.Integer");
    				idNode.addAttribute("column", "id");
    				Element generatorNode = idNode.addElement("generator");
    				generatorNode.addAttribute("class", "native");
    				Map inheritedMetas = new HashMap();
    				HbmBinder.bindRootClass(node, rootClass, mappings, inheritedMetas);
    				if (tableName == null) {
	    				String simpleName = beanIntrospector.getSimpleName(entityName);
	    				int index = simpleName.indexOf('.');
	    				String siteName = index > 0 ? simpleName.substring(0, index) : simpleName;
	    				tableName = siteName + '_' + rootClass.getTable().getName();
	    				rootClass.getTable().setName(tableName);
    				}
    				mappings.addClass(rootClass);
    				
    				persistentClass = rootClass;
    			}
    			
    			if (entityClazz != null) {
    				DiscriminatorColumn discriminatorColumn = (DiscriminatorColumn) entityClazz.getAnnotation(DiscriminatorColumn.class);
    				if (discriminatorColumn != null && persistentClass instanceof RootClass) {
    					RootClass rootClass = (RootClass)persistentClass;
    					SimpleValue discriminator = new SimpleValue(mappings, rootClass.getTable());
    					Column column = new Column(discriminatorColumn.name());
    					discriminator.addColumn(column);
    					String typeName;
    					DiscriminatorType type = discriminatorColumn.discriminatorType();
    					switch (type) {
    					case STRING:
    						typeName = String.class.getName();
    						break;
    					case CHAR:
    						typeName = Character.class.getName();
    						break;
    					case INTEGER:
    						typeName = Integer.class.getName();
    						break;
    					default:
    						typeName = String.class.getName();
    					}
    					discriminator.setTypeName(typeName);
    					rootClass.setDiscriminator(discriminator);
    				}
    				
    				Entity entity = (Entity) entityClazz.getAnnotation(Entity.class);
    				if (entity != null && entity.name().length() > 0) {
    					persistentClass.setNodeName(entity.name());
    					
    					// default discriminatorValue is the enity name
    					DiscriminatorValue discriminatorValue = (DiscriminatorValue) entityClazz.getAnnotation(DiscriminatorValue.class);
    					if (discriminatorValue == null) {
    						persistentClass.setDiscriminatorValue(entity.name());
    					}
    				}
    				
    				TypeDefs typeDefs = (TypeDefs) entityClazz.getAnnotation(TypeDefs.class);
    				if (typeDefs != null) {
    					for (TypeDef typeDef : typeDefs.value()) {
    						Class typeClass = typeDef.typeClass();
    						String typeName = typeDef.name();
    						Parameter [] paramIters = typeDef.parameters();
    						Properties parameters = new Properties();
    						for (Parameter param : paramIters) {
    							parameters.setProperty(param.name(), param.value());
    						}
    						mappings.addTypeDef(typeName, typeClass.getName(), parameters);
    					}
    				}
    			}
    			newDef = true;
    		}
    	} else if (isCurrentEntity) {
    		// clone it
    		PersistentClass newInstance;
    		Class clazz = persistentClass.getClass();
    		if (clazz.equals(RootClass.class)) {
    			newInstance = new RootClass();
    		} else if (clazz.equals(Subclass.class)) {
    			newInstance = new Subclass(persistentClass.getSuperclass());
    		} else if (clazz.equals(JoinedSubclass.class)) {
    			newInstance = new JoinedSubclass(persistentClass.getSuperclass());
    		} else if (clazz.equals(SingleTableSubclass.class)) {
    			newInstance = new SingleTableSubclass(persistentClass.getSuperclass());
    		} else if (clazz.equals(UnionSubclass.class)) {
    			newInstance = new UnionSubclass(persistentClass.getSuperclass());
    		} else {
    			newInstance = null;
    			logger.error("Found unknown PersistentClass type: " + clazz.getName());
    		}
    		
    		// FIXME: use copy & reference or just keep the add & remove operation in the fields
//    		if (newInstance != null) {
//	    		ModelUtil.clone(persistentClass, newInstance, false);
//	    		persistentClass = newInstance;
//    		}
    	}
    	
    	if (isCurrentEntity) {		// update the status of current entity
    		this.persistentClass = persistentClass;
    		this.embeddableClass = embeddableClass;
    		this.newDef = newDef;
    	}
    	return persistentClass;
    }
}